// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// TCP sockets

package net

import (
	"errors"
	"io"
	"os"
	"runtime"
	"sync"
	"syscall"
	"time"
)

// TCPConn is an implementation of the Conn interface
// for TCP network connections.
type TCPConn struct {
	sync.Mutex

	initialized bool

	readChan   chan int
	readBuffer []byte
	readError  error

	socketID                    int
	readDeadline, writeDeadline time.Time
	readClosed, writeClosed     bool

	laddr, raddr *TCPAddr
}

var ip6NotSupportedYet = errors.New("IPv6 support is not implemented yet")

func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error) {
	if raddr == nil {
		return nil, &OpError{"dial", net, nil, errMissingAddress}
	}

	if ip4 := raddr.IP.To4(); ip4 != nil {
		sid, err := syscall.Socket(syscall.PF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
		if err != nil {
			return nil, &OpError{"socket", net, nil, err}
		}

		if laddr != nil {
			err = syscall.Bind(sid, laddr.toSockAddr4())
			if err != nil {
				return nil, &OpError{"bind", net, laddr, err}
			}
		}

		err = syscall.Connect(sid, raddr.toSockAddr4())
		if err != nil {
			return nil, &OpError{"connect", net, raddr, err}
		}

		return &TCPConn{
			initialized: true,
			socketID:    sid,
			laddr:       laddr,
			raddr:       raddr,
		}, nil
	}

	// TODO: support IPv6
	return nil, ip6NotSupportedYet
}

func (c *TCPConn) ok() bool {
	return c != nil && c.initialized
}

// SetDeadline implements the Conn SetDeadline method.
func (c *TCPConn) SetDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	c.readDeadline = t
	c.writeDeadline = t
	return nil
}

// SetReadDeadline implements the Conn SetReadDeadline method.
func (c *TCPConn) SetReadDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	c.readDeadline = t
	return nil
}

// SetWriteDeadline implements the Conn SetWriteDeadline method.
func (c *TCPConn) SetWriteDeadline(t time.Time) error {
	if !c.ok() {
		return syscall.EINVAL
	}
	c.writeDeadline = t
	return nil
}

func (c *TCPConn) close() {
	syscall.CloseSocket(c.socketID)
	runtime.SetFinalizer(c, nil) // No longer needed.
}

func (c *TCPConn) Close() error {
	if !c.ok() {
		return syscall.EINVAL
	}
	c.Lock()
	defer c.Unlock()

	if !c.readClosed || !c.writeClosed {
		c.readClosed = true
		c.writeClosed = true
		c.close()
	}
	return nil
}

// On HelenOS, one-sided closes are a no-op. Just marks the connection as closed for program's sake.
func (c *TCPConn) CloseRead() error {
	if !c.ok() {
		return syscall.EINVAL
	}
	c.Lock()
	defer c.Unlock()

	if !c.readClosed {
		c.readClosed = true
		if c.writeClosed {
			c.close()
		}
	}
	return nil
}

// On HelenOS, one-sided closes are a no-op. Just marks the connection as closed for program's sake.
func (c *TCPConn) CloseWrite() error {
	if !c.ok() {
		return syscall.EINVAL
	}
	c.Lock()
	defer c.Unlock()

	if !c.writeClosed {
		c.writeClosed = true
		if c.readClosed {
			c.close()
		}
	}
	return nil
}

func (c *TCPConn) File() (f *os.File, err error) {
	if !c.ok() {
		return nil, syscall.EINVAL
	}
	return nil, helenosSupportError
}

func (c *TCPConn) LocalAddr() Addr {
	return c.laddr
}

type writerOnly struct {
	io.Writer
}

func (c *TCPConn) ReadFrom(r io.Reader) (int64, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	c.Lock()
	defer c.Unlock()

	// Use wrapper to hide existing r.ReadFrom from io.Copy.
	return io.Copy(writerOnly{c}, r)
}

func (c *TCPConn) RemoteAddr() Addr {
	return c.raddr
}

func (c *TCPConn) SetKeepAlive(keepalive bool) error {
	if !c.ok() {
		return syscall.EINVAL
	}

	// TODO
	return helenosSupportError
}

func (c *TCPConn) SetLinger(sec int) error {
	if !c.ok() {
		return syscall.EINVAL
	}

	// TODO
	return helenosSupportError
}

func (c *TCPConn) SetNoDelay(noDelay bool) error {
	if !c.ok() {
		return syscall.EINVAL
	}

	// TODO
	return helenosSupportError
}

func (c *TCPConn) SetReadBuffer(bytes int) error {
	if !c.ok() {
		return syscall.EINVAL
	}

	//TODO
	return helenosSupportError
}

func (c *TCPConn) SetWriteBuffer(bytes int) error {
	if !c.ok() {
		return syscall.EINVAL
	}

	// TODO
	return helenosSupportError
}

func (c *TCPConn) read() {
	n, err := syscall.Recv(c.socketID, c.readBuffer, 0)
	if err != nil {
		c.readBuffer = nil
		c.readError = os.NewSyscallError("TCPConn.Read", err)
	} else {
		c.readBuffer = c.readBuffer[:n]
		c.readError = nil
	}
}

// This Read implementation honors deadline, even though the OS doesn't support it.
// If the deadline is not met, the function returns but the read continues in the background.
// Next Read call continues the read started by the previous call,
// and because the read already started based on the earlier buffer size, it may return less
// bytes than requested. All received bytes are guaranteed to be returned by some successful
// Read call; no data are ever dropped.
func (c *TCPConn) Read(b []byte) (n int, err error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	c.Lock()
	defer c.Unlock()

	if c.readClosed {
		return 0, syscall.EINVAL
	}

	if c.readDeadline.IsZero() && c.readChan == nil {
		n, err = syscall.Recv(c.socketID, b, 0)
		if err != nil {
			err = os.NewSyscallError("TCPConn.Read", err)
		}
		return n, err
	}

	if !c.readDeadline.IsZero() && time.Now().After(c.readDeadline) {
		return 0, errTimeout
	}

	var timeoutChan <-chan time.Time
	if c.readDeadline.IsZero() {
		timeoutChan = nil
	} else {
		timeoutChan = time.After(c.readDeadline.Sub(time.Now()))
	}

	if c.readChan == nil {
		c.readBuffer = make([]byte, len(b))
		c.readChan = make(chan int, 1)
		c.readError = nil

		go func() {
			c.read()
			c.readChan <- 1
		}()
	}

	select {
	case <-c.readChan:
		n = len(c.readBuffer)
		err = c.readError

		if err != nil {
			c.readBuffer = nil
			c.readChan = nil
			c.readError = nil
			return 0, err
		}

		copy(b, c.readBuffer)
		if len(b) < len(c.readBuffer) {
			c.readBuffer = c.readBuffer[len(b):]
			c.readChan <- 1
		} else {
			c.readBuffer = nil
			c.readChan = nil
		}

		return n, err
	case <-timeoutChan:
		return 0, errTimeout
	}

	panic("unreachable")
}

// This Write implementation honors deadline, even though the OS doesn't support it.
// If the deadline is not met, the function returns but the write continues in the background until
// it completes or fails.
func (c *TCPConn) Write(b []byte) (n int, err error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	c.Lock()
	defer c.Unlock()

	if c.writeClosed {
		return 0, syscall.EINVAL
	}

	if c.writeDeadline.IsZero() {
		n, err = syscall.Send(c.socketID, b, 0)
		if err != nil {
			err = os.NewSyscallError("TCPConn.Write", err)
		}
		return n, err
	}

	writeDoneChan := make(chan interface{}, 1)
	timeoutChan := time.After(c.writeDeadline.Sub(time.Now()))

	go func() {
		n, err := syscall.Send(c.socketID, b, 0)
		if err != nil {
			writeDoneChan <- err
		} else {
			writeDoneChan <- n
		}
	}()

	select {
	case result := <-writeDoneChan:
		if err, ok := result.(error); ok {
			return 0, os.NewSyscallError("TCPConn.Write", err)
		}
		return result.(int), nil
	case <-timeoutChan:
		return 0, errTimeout
	}

	panic("unreachable")
}

func (addr *TCPAddr) toSockAddr4() *syscall.SockAddrIn {
	ip4 := addr.IP.To4()
	if ip4 == nil {
		return nil
	}
	port := addr.Port

	return &syscall.SockAddrIn{
		Family: uint16(syscall.AF_INET),
		Port:   uint16(port),
		Addr:   [4]byte{ip4[0], ip4[1], ip4[2], ip4[3]},
	}
}

func (addr *TCPAddr) toSockAddr6() *syscall.SockAddrIn6 {
	ip6 := addr.IP.To16()
	if ip6 == nil {
		return nil
	}
	port := addr.Port

	return &syscall.SockAddrIn6{
		Family:   uint16(syscall.AF_INET6),
		Port:     uint16(port),
		FlowInfo: 0,
		Addr: [16]byte{ip6[0], ip6[1], ip6[2], ip6[3],
			ip6[4], ip6[5], ip6[6], ip6[7],
			ip6[8], ip6[9], ip6[10], ip6[11],
			ip6[12], ip6[13], ip6[14], ip6[15]},
		ScopeID: 0,
	}
}

func sockAddrToTCPAddr(sockaddr syscall.SockAddr) *TCPAddr {
	switch addr := sockaddr.(type) {
	case *syscall.SockAddrIn:
		a := make([]byte, 4)
		copy(a, addr.Addr[:])
		return &TCPAddr{IP(a), int(addr.Port)}
	case *syscall.SockAddrIn6:
		a := make([]byte, 16)
		copy(a, addr.Addr[:])
		return &TCPAddr{IP(a), int(addr.Port)}
	}
	return nil
}
